package com.qinchen.springmvc.contenxt;

import com.qinchen.springmvc.annotation.Autowired;
import com.qinchen.springmvc.annotation.Controller;
import com.qinchen.springmvc.annotation.Service;
import com.qinchen.springmvc.xml.XmlPaser;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author qinchen
 * @date 2021/1/16 18:03
 * 向未来的自己致敬
 * 描述：SpringMVC容器
 */
public class WebApplicationContext {

    /**
     * 值为 classpath:springmvc.xml
     */
    private String contextConfigLocation;

    /**
     * 定义集合  用于存放 bean 的权限名|包名.类名
     */
    List<String> classNameList = new ArrayList<String>();

    /**
     * 创建Map集合用于扮演IOC容器：  key存放bean的名字   value存放bean实例
     */
    public Map<String, Object> iocMap = new ConcurrentHashMap<>();

    public WebApplicationContext() {

    }

    public WebApplicationContext(String contextConfigLocation) {
        this.contextConfigLocation = contextConfigLocation;
    }

    /**
     * 容器初始化操作
     */
    public void onRefresh() {

        // 1、读配置文件，进行解析springmvc配置文件操作
        String pack = XmlPaser.getbasePackage(contextConfigLocation.split(":")[1]);

        // 2、进行包扫描
        String[] packs = pack.split(",");

        for (String pa : packs) {
            excuteScanPackage(pa);
        }

        // 3、实例化容器中bean
        executeInstance();

        // 4、进行自动注入操作
        executeAutoWired();

    }

    /**
     * 扫描包
     */
    public void excuteScanPackage(String pack) {
        // 获取到包所在路径
        URL url = this.getClass().getClassLoader().getResource("/" + pack.replaceAll("\\.", "/"));
        String path = url.getFile();
        // 以路径获得文件夹对象
        File dir = new File(path);
        // 递归调用，读取类文件全限定名，放入集合中
        for (File f : dir.listFiles()) {
            if (f.isDirectory()) {
                // 是目录，继续递归
                excuteScanPackage(pack + "." + f.getName());
            } else {
                // 是文件，放入集合
                String className = pack + "." + f.getName().replaceAll(".class", "");
                classNameList.add(className);
            }
        }
    }

    /**
     * 实例化容器中的bean
     */
    public void executeInstance() {

        try {
            // 遍历所有类
            for (String className : classNameList) {
                // 反射，获取到类字节码对象
                Class<?> clazz = Class.forName(className);

                if (clazz.isAnnotationPresent(Controller.class)) {
                    // 如果是控制器
                    String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() + clazz.getSimpleName().substring(1);
                    iocMap.put(beanName, clazz.newInstance());

                } else if (clazz.isAnnotationPresent(Service.class)) {
                    // 如果是Service
                    Service serviceAn = clazz.getAnnotation(Service.class);
                    String beanName = serviceAn.value();
                    iocMap.put(beanName, clazz.newInstance());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 进行自动注入操作
     */
    private void executeAutoWired() {
        try {
            // 从容器中取出 bean，然后判断bean中是否有属性上使用Autowired，如果使用了该注解，就需要进行自动注入操作
            for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
                // 获取容器中的bean
                Object bean = entry.getValue();
                // 获取bean中的属性
                Field[] fields = bean.getClass().getDeclaredFields();
                for (Field field : fields) {
                    if (field.isAnnotationPresent(Autowired.class)) {
                        // 获取注解中的value值|该值就是bean的name
                        Autowired autoWiredAno = field.getAnnotation(Autowired.class);
                        String beanName = autoWiredAno.value();
                        // 如果是私有熟悉，需要取消检查机制
                        field.setAccessible(true);
                        field.set(bean, iocMap.get(beanName));
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
